package com.wys.spring.controller.servlet;

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.wys.utils.JsonUtils;
import com.wys.utils.StringUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


public class RequestPathMappingHandler extends RequestMappingInfoHandlerMapping {

    /**
     * 后续可做扩展,例如将所有的url路径动态的扫描到数据库表中做存放,并加以角色配置对应的请求路径
     */
    private final Map<String, RequestMappingInfo> mappingInfoMap = new ConcurrentHashMap<>();

    /**
     * 系统中想要排除的请求路径配合mappingInfoMap这个集合可以做到动态的控制用户的请求权限以及当前服务对外接口的访问权限控制
     */
    private Set<String> excludePaths = new HashSet<>();

    //todo 后续做成可数据库配置，也可动态手写两种配置策略
    public RequestPathMappingHandler(RequestServletProperties requestServletProperties) {
        if (requestServletProperties != null && StringUtils.isNotEmpty(requestServletProperties.getExcludePaths())) {
            Arrays.asList(requestServletProperties.getExcludePaths().split(",")).forEach(a -> {
                if ("/".startsWith(StringUtils.substring(a, 0))) {
                    excludePaths.add(a);
                } else {
                    excludePaths.add("/".concat(a));
                }
            });
        }
    }

    @Override
    protected boolean isHandler(Class<?> beanType) {
        return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
    }

    public void setExcludePaths(Set<String> excludePaths) {
        this.excludePaths = excludePaths;
    }

    public Set<String> getExcludePaths() {
        return excludePaths;
    }

    public Map<String, RequestMappingInfo> getAllPathMap() {
        return mappingInfoMap;
    }

    public Set<String> getAllPath() {
        return mappingInfoMap.keySet();
    }


    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = this.createRequestMappingInfo(method);
        if (info != null) {
            RequestMappingInfo typeInfo = this.createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }
            if (ObjectUtils.isNotEmpty(info.getDirectPaths())) {
                mappingInfoMap.put(new ArrayList<>(info.getDirectPaths()).get(0), info);
                if (ObjectUtils.isNotEmpty(excludePaths)) {
                    for (String excludePath : excludePaths) {
                        if (info.getDirectPaths().contains(excludePath)) {
                            super.logger.warn("检测到存在排除的RequestPath：" + JsonUtils.object2Json(excludePath));
                            return null;
                        }
                    }
                }
            }
        }
        return info;
    }


    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = (RequestMapping) AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = element instanceof Class ? this.getCustomTypeCondition((Class) element) : this.getCustomMethodCondition((Method) element);
        return requestMapping != null ? this.createRequestMappingInfo(requestMapping, condition) : null;
    }

    @Nullable
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        return null;
    }

    @Nullable
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        return null;
    }

    protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
        RequestMappingInfo.Builder builder = RequestMappingInfo.paths(requestMapping.path()).methods(requestMapping.method()).params(requestMapping.params()).headers(requestMapping.headers()).consumes(requestMapping.consumes()).produces(requestMapping.produces()).mappingName(requestMapping.name());
        if (customCondition != null) {
            builder.customCondition(customCondition);
        }
        return builder.build();
    }


}
